package com.tkjt.pms.calc.formula.excel.core;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.tkjt.pms.calc.formula.FormulaEngine;
import com.tkjt.pms.calc.formula.common.EngineResult;
import com.tkjt.pms.calc.formula.common.FormulaCells;
import com.tkjt.pms.calc.formula.common.FormulaConsts;
import com.tkjt.pms.calc.formula.common.FormulaRule;
import com.tkjt.pms.calc.formula.common.FormulaValue;
import com.tkjt.pms.calc.formula.common.FormulaVariableCells;
import com.tkjt.pms.calc.formula.excel.builder.DataKeyBuilder;
import com.tkjt.pms.calc.formula.excel.component.FormulaComponent;
import com.tkjt.pms.calc.formula.utils.FormulaParamHelper;
import com.tkjt.pms.calc.formula.utils.FormulaUtils;
import com.tkjt.pms.calc.utils.EsCalcUtils;
import com.tkjt.pms.common.IndexConsts;
import com.tkjt.pms.common.IndexParam;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component("excelEngine")
public final class FormulaEngineImpl implements FormulaEngine {

	@Autowired
	private Map<String, DataKeyBuilder> dataKeyBuilders = new HashMap<String, DataKeyBuilder>();
	@Autowired
	protected FormulaComponent formulaComponent;

	@Override
	public EngineResult doExecutor(IndexParam indexParam, FormulaRule formulaRule, FormulaValue formulaValue) {
		EngineResult engineResult = new EngineResult();
		try {
			// [获取公式信息&验证]
			engineResult = this.checkFormulaParam(indexParam);
			if (!engineResult.isSuccess()) {
				return engineResult;
			}
			String whichData = FormulaParamHelper.getFormulaCurrentWhichData(indexParam);
			String indexYearMonth = FormulaParamHelper.getFormulaIndexYearMonth(indexParam);
			log.info("whichData={},indexYearMonth={}", whichData, indexYearMonth);
			// 获取计算数据列: mTotal, m01,...,m12,q01,...,q04
			List<String> calcDataColumns = this.getCalcDataColumns(whichData, indexYearMonth);
			for (String dataColumn : calcDataColumns) {
				FormulaCells formulaCells = this.buildExcelFormulaAndCells(dataColumn, indexYearMonth, formulaRule, formulaValue);
				engineResult.addCalcFormulaCells(dataColumn, formulaCells);
				String excelFormula = formulaCells.getCellFormula();
				Map<String, String> excelCells = formulaCells.getCellValues();
				// 执行Excel公式运算
				Double retObj = Double.valueOf(formulaComponent.executeFormula(excelCells, excelFormula).toString());
				if(retObj!=null && (retObj.isNaN() || retObj.isInfinite())) {
					engineResult.addCalcColumnValue(dataColumn, null);
				} else {
					engineResult.addCalcColumnValue(dataColumn, retObj);
				}
			}
		} catch (Exception e) {
			engineResult.setRetCode(FormulaConsts.RET_FAIL);
			engineResult.setRetMsg(e.getMessage());
			log.error(e.getMessage(), e);
			log.error("indexParam={}", indexParam);
			log.error("formulaRule={}", formulaRule);
			log.error("formulaValue={}", formulaValue);
		}
		return engineResult;
	}

	private FormulaCells buildExcelFormulaAndCells(String dataColumn, String indexYearMonth, FormulaRule formulaRule, FormulaValue formulaValue) {
		FormulaCells formulaCells = new FormulaCells();
		formulaCells.setCellFormula(formulaRule.getFormula());
		int lineNumber = 1;
		Set<Entry<String, List<Double>>> entries = formulaValue.getValues().entrySet();
		for (Entry<String, List<Double>> entry : entries) {
			String variable = entry.getKey();
			List<Double> variableValues = entry.getValue();
			DataKeyBuilder dataKeyBuilder = this.route2DataKeyBuilder(variable, dataColumn);
			if (null == dataKeyBuilder) {
				continue;
			}
			FormulaVariableCells formulaVariableCells = null;
			formulaVariableCells = dataKeyBuilder.doBuilder(formulaCells.getCellFormula(), variable, variableValues, lineNumber, dataColumn, indexYearMonth);
			if (null == formulaVariableCells) {
				continue;
			}
			formulaCells.updateFormulaCells(formulaVariableCells);
			lineNumber += 1;
		}
		return formulaCells;
	}
	
	private DataKeyBuilder route2DataKeyBuilder(String variable, String dataColumn) {
		DataKeyBuilder dataKeyBuilder = dataKeyBuilders.get(FormulaUtils.formatDataKey(variable));
		if (null != dataKeyBuilder) {
			return dataKeyBuilder;
		} 
		if(dataColumn.startsWith("mTotal") || dataColumn.trim().toLowerCase().startsWith("mtotal")) {
			return dataKeyBuilders.get("mTotal");
		}
		if(dataColumn.startsWith("m") || dataColumn.startsWith("M")) {
			return dataKeyBuilders.get("Mi");
		}
		if(dataColumn.startsWith("q") || dataColumn.startsWith("Q")) {
			return dataKeyBuilders.get("Qi");
		}
		return null;
	}

	private EngineResult checkFormulaParam(IndexParam indexParam) {
		EngineResult result = new EngineResult();

		if (null == indexParam.getParam(FormulaConsts.ITEM_FORMULA_WHICH_DATA)) {
			result.setRetCode(FormulaConsts.RET_FAIL);
			result.setRetMsg("formula which data == null.");
			log.error(result.getRetMsg());
			return result;
		}
		
		if (null == indexParam.getParam(IndexConsts.PARAM_INDEX_YEAR_MONTH)) {
			result.setRetCode(FormulaConsts.RET_FAIL);
			result.setRetMsg("param index year month == null.");
			log.error(result.getRetMsg());
			return result;
		}
		
		result.setRetCode(FormulaConsts.RET_SUCCESS);
		return result;
	}

	private List<String> getCalcDataColumns(String whichData, String indexYearMonth) {
		int indexMi = EsCalcUtils.getCalcMi(indexYearMonth);
		int indexQi = EsCalcUtils.getCalcQi(indexYearMonth);
		List<String> calcDataColumns = new ArrayList<String>();
		if ("Mi".equalsIgnoreCase(whichData)) {
			for (int index = 1; index <= indexMi; index++) {
				if (index < 10) {
					calcDataColumns.add("m0" + index);
				} else {
					calcDataColumns.add("m" + index);
				}
			}
		} else if ("Qi".equalsIgnoreCase(whichData)) {
			for (int index = 1; index <= indexQi; index++) {
				calcDataColumns.add("q0" + index);
			}
		} else {
			calcDataColumns.add(whichData);
		}
		return calcDataColumns;
	}

}
